/*
 * Copyright (c) 2023-2023 elsfs Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.elsfs.cloud.oss.api;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectResult;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.amazonaws.util.IOUtils;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.elsfs.cloud.oss.api.properties.FileProperties;
import org.springframework.beans.factory.InitializingBean;

/**
 * aws-s3 通用存储操作 支持所有兼容s3协议的云存储: {阿里云OSS，腾讯云COS，七牛云，京东云，minio 等}
 *
 * @author zeng
 */
@RequiredArgsConstructor
public class OssTemplate implements InitializingBean, FileTemplate {

  private final FileProperties properties;

  private AmazonS3 amazonS3;

  /**
   * 创建桶
   *
   * @param bucketName bucket名称
   */
  @SneakyThrows
  public void createBucket(String bucketName) {
    if (!amazonS3.doesBucketExistV2(bucketName)) {
      amazonS3.createBucket((bucketName));
    }
  }

  @SneakyThrows
  public List<Bucket> getAllBuckets() {
    return amazonS3.listBuckets();
  }

  /**
   * 获取指定名称的S3存储桶。
   *
   * @param bucketName 存储桶的名称。
   * @return 如果找到具有指定名称的存储桶，则返回Optional包含该存储桶；如果没有找到，则返回空的Optional。
   */
  @SneakyThrows
  public Optional<Bucket> getBucket(String bucketName) {
    // 从S3服务列出所有存储桶，并通过名称过滤，返回第一个匹配的存储桶。
    return amazonS3.listBuckets().stream().filter(b -> b.getName().equals(bucketName)).findFirst();
  }

  /**
   * 删除指定的S3存储桶。
   *
   * @param bucketName 需要删除的S3存储桶的名称。
   */
  @SneakyThrows
  public void removeBucket(String bucketName) {
    amazonS3.deleteBucket(bucketName); // 直接调用AmazonS3实例的deleteBucket方法删除指定的存储桶
  }

  /**
   * 通过指定前缀获取S3存储桶中的所有对象。
   *
   * @param bucketName 要查询的S3存储桶名称。
   * @param prefix 指定的对象前缀，用于过滤对象。
   * @param recursive 是否递归查询。如果为true，则会查询匹配前缀的所有对象，包括子目录中的对象；如果为false，则只查询当前目录下的对象。
   * @return 包含所有匹配对象的列表。
   */
  @SneakyThrows
  public List<S3ObjectSummary> getAllObjectsByPrefix(
      String bucketName, String prefix, boolean recursive) {
    // 列出指定存储桶和前缀的所有对象
    ObjectListing objectListing = amazonS3.listObjects(bucketName, prefix);
    // 将对象概要列表转换为ArrayList并返回
    return new ArrayList<>(objectListing.getObjectSummaries());
  }

  /**
   * 获取文件url
   *
   * @param bucketName 桶名称
   * @param objectName 文件名称
   * @param expires 到期时间
   * @return url
   */
  @SneakyThrows
  public String getObjectURL(String bucketName, String objectName, Integer expires) {
    Date date = new Date();
    Calendar calendar = new GregorianCalendar();
    calendar.setTime(date);
    calendar.add(Calendar.DAY_OF_MONTH, expires);
    URL url = amazonS3.generatePresignedUrl(bucketName, objectName, calendar.getTime());
    return url.toString();
  }

  @SneakyThrows
  public S3Object getObject(String bucketName, String objectName) {
    return amazonS3.getObject(bucketName, objectName);
  }

  public void putObject(String bucketName, String objectName, InputStream stream) throws Exception {
    putObject(bucketName, objectName, stream, stream.available(), "application/octet-stream");
  }

  public void putObject(
      String bucketName, String objectName, InputStream stream, String contextType)
      throws Exception {
    putObject(bucketName, objectName, stream, stream.available(), contextType);
  }

  /**
   * 更新文件
   *
   * @param bucketName 桶名称
   * @param objectName 文件名称
   * @param stream 文件的流
   * @param size 文件大小
   * @param contextType 类型
   * @return 更新信息
   * @throws Exception e
   */
  public PutObjectResult putObject(
      String bucketName, String objectName, InputStream stream, long size, String contextType)
      throws Exception {
    // String fileName = getFileName(objectName);
    byte[] bytes = IOUtils.toByteArray(stream);
    ObjectMetadata objectMetadata = new ObjectMetadata();
    objectMetadata.setContentLength(size);
    objectMetadata.setContentType(contextType);
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
    // 上传
    return amazonS3.putObject(bucketName, objectName, byteArrayInputStream, objectMetadata);
  }

  /**
   * 从指定的存储桶中获取对象的信息。
   *
   * @param bucketName 存储桶的名称，指定要从中获取对象信息的存储桶。
   * @param objectName 对象的名称，指定要获取信息的对象。
   * @return S3Object 返回从S3存储桶中获取到的对象信息。
   */
  public S3Object getObjectInfo(String bucketName, String objectName) {
    // 从指定的存储桶和对象名称获取S3对象
    S3Object object = amazonS3.getObject(bucketName, objectName);
    return object;
  }

  @Override
  public void removeObject(String bucketName, String objectName) {
    amazonS3.deleteObject(bucketName, objectName);
  }

  @Override
  public void afterPropertiesSet() {
    ClientConfiguration clientConfiguration = new ClientConfiguration();
    clientConfiguration.setMaxConnections(properties.getOss().getMaxConnections());

    AwsClientBuilder.EndpointConfiguration endpointConfiguration =
        new AwsClientBuilder.EndpointConfiguration(
            properties.getOss().getEndpoint(), properties.getOss().getRegion());
    AWSCredentials awsCredentials =
        new BasicAWSCredentials(
            properties.getOss().getAccessKey(), properties.getOss().getSecretKey());
    AWSCredentialsProvider awsCredentialsProvider =
        new AWSStaticCredentialsProvider(awsCredentials);
    this.amazonS3 =
        AmazonS3Client.builder()
            .withEndpointConfiguration(endpointConfiguration)
            .withClientConfiguration(clientConfiguration)
            .withCredentials(awsCredentialsProvider)
            .disableChunkedEncoding()
            .withPathStyleAccessEnabled(properties.getOss().getPathStyleAccess())
            .build();
  }
}
